<!DOCTYPE html>
<!-- saved from url=(0038)http://gabrielecirulli.github.io/2048/ -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta charset="utf-8">
  <title>2048</title>

  <!--link href="./2048_files/main.css" rel="stylesheet" type="text/css"-->
  <style>
  /*@import url(fonts/clear-sans.css);*/
html, body {
  margin: 0;
  padding: 0;
  background: #faf8ef;
  color: #776e65;
  font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif;
  font-size: 18px; }

body {
  margin: 80px 0; }

.heading:after {
  content: "";
  display: block;
  clear: both; }

h1.title {
  font-size: 80px;
  font-weight: bold;
  margin: 0;
  display: block;
  float: left; }

@-webkit-keyframes move-up {
  0% {
    top: 25px;
    opacity: 1; }

  100% {
    top: -50px;
    opacity: 0; } }

@-moz-keyframes move-up {
  0% {
    top: 25px;
    opacity: 1; }

  100% {
    top: -50px;
    opacity: 0; } }

@keyframes move-up {
  0% {
    top: 25px;
    opacity: 1; }

  100% {
    top: -50px;
    opacity: 0; } }

.scores-container {
  float: right;
  text-align: right; }

.score-container, .best-container {
  position: relative;
  display: inline-block;
  background: #bbada0;
  padding: 15px 25px;
  font-size: 25px;
  height: 25px;
  line-height: 47px;
  font-weight: bold;
  border-radius: 3px;
  color: white;
  margin-top: 8px;
  text-align: center; }
  .score-container:after, .best-container:after {
    position: absolute;
    width: 100%;
    top: 10px;
    left: 0;
    text-transform: uppercase;
    font-size: 13px;
    line-height: 13px;
    text-align: center;
    color: #eee4da; }
  .score-container .score-addition, .best-container .score-addition {
    position: absolute;
    right: 30px;
    color: red;
    font-size: 25px;
    line-height: 25px;
    font-weight: bold;
    color: rgba(119, 110, 101, 0.9);
    z-index: 100;
    -webkit-animation: move-up 600ms ease-in;
    -moz-animation: move-up 600ms ease-in;
    -webkit-animation-fill-mode: both;
    -moz-animation-fill-mode: both; }

.score-container:after {
  content: "Score"; }

.best-container:after {
  content: "Best"; }

p {
  margin-top: 0;
  margin-bottom: 10px;
  line-height: 1.65; }

a {
  color: #776e65;
  font-weight: bold;
  text-decoration: underline;
  cursor: pointer; }

strong.important {
  text-transform: uppercase; }

hr {
  border: none;
  border-bottom: 1px solid #d8d4d0;
  margin-top: 20px;
  margin-bottom: 30px; }

.container {
  width: 500px;
  margin: 0 auto; }

@-webkit-keyframes fade-in {
  0% {
    opacity: 0; }

  100% {
    opacity: 1; } }

@-moz-keyframes fade-in {
  0% {
    opacity: 0; }

  100% {
    opacity: 1; } }

@keyframes fade-in {
  0% {
    opacity: 0; }

  100% {
    opacity: 1; } }

.game-container {
  margin-top: 40px;
  position: relative;
  padding: 15px;
  cursor: default;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  background: #bbada0;
  border-radius: 6px;
  width: 500px;
  height: 500px;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box; }
  .game-container .game-message {
    display: none;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: rgba(238, 228, 218, 0.5);
    z-index: 100;
    text-align: center;
    -webkit-animation: fade-in 800ms ease 1200ms;
    -moz-animation: fade-in 800ms ease 1200ms;
    -webkit-animation-fill-mode: both;
    -moz-animation-fill-mode: both; }
    .game-container .game-message p {
      font-size: 60px;
      font-weight: bold;
      height: 60px;
      line-height: 60px;
      margin-top: 222px; }
    .game-container .game-message .lower {
      display: block;
      margin-top: 59px; }
    .game-container .game-message a {
      display: inline-block;
      background: #8f7a66;
      border-radius: 3px;
      padding: 0 20px;
      text-decoration: none;
      color: #f9f6f2;
      height: 40px;
      line-height: 42px;
      margin-left: 9px; }
    .game-container .game-message .score-sharing {
      display: inline-block;
      vertical-align: middle;
      margin-left: 10px; }
    .game-container .game-message.game-won {
      background: rgba(237, 194, 46, 0.5);
      color: #f9f6f2; }
    .game-container .game-message.game-won, .game-container .game-message.game-over {
      display: block; }

.grid-container {
  position: absolute;
  z-index: 1; }

.grid-row {
  margin-bottom: 15px; }
  .grid-row:last-child {
    margin-bottom: 0; }
  .grid-row:after {
    content: "";
    display: block;
    clear: both; }

.grid-cell {
  width: 106.25px;
  height: 106.25px;
  margin-right: 15px;
  float: left;
  border-radius: 3px;
  background: rgba(238, 228, 218, 0.35); }
  .grid-cell:last-child {
    margin-right: 0; }

.tile-container {
  position: absolute;
  z-index: 2; }

.tile {
  width: 106.25px;
  height: 106.25px;
  line-height: 116.25px; }
  .tile.tile-position-1-1 {
    position: absolute;
    left: 0px;
    top: 0px; }
  .tile.tile-position-1-2 {
    position: absolute;
    left: 0px;
    top: 121px; }
  .tile.tile-position-1-3 {
    position: absolute;
    left: 0px;
    top: 243px; }
  .tile.tile-position-1-4 {
    position: absolute;
    left: 0px;
    top: 364px; }
  .tile.tile-position-2-1 {
    position: absolute;
    left: 121px;
    top: 0px; }
  .tile.tile-position-2-2 {
    position: absolute;
    left: 121px;
    top: 121px; }
  .tile.tile-position-2-3 {
    position: absolute;
    left: 121px;
    top: 243px; }
  .tile.tile-position-2-4 {
    position: absolute;
    left: 121px;
    top: 364px; }
  .tile.tile-position-3-1 {
    position: absolute;
    left: 243px;
    top: 0px; }
  .tile.tile-position-3-2 {
    position: absolute;
    left: 243px;
    top: 121px; }
  .tile.tile-position-3-3 {
    position: absolute;
    left: 243px;
    top: 243px; }
  .tile.tile-position-3-4 {
    position: absolute;
    left: 243px;
    top: 364px; }
  .tile.tile-position-4-1 {
    position: absolute;
    left: 364px;
    top: 0px; }
  .tile.tile-position-4-2 {
    position: absolute;
    left: 364px;
    top: 121px; }
  .tile.tile-position-4-3 {
    position: absolute;
    left: 364px;
    top: 243px; }
  .tile.tile-position-4-4 {
    position: absolute;
    left: 364px;
    top: 364px; }

.tile {
  border-radius: 3px;
  background: #eee4da;
  text-align: center;
  font-weight: bold;
  z-index: 10;
  font-size: 55px;
  -webkit-transition: 100ms ease-in-out;
  -moz-transition: 100ms ease-in-out;
  -webkit-transition-property: top, left;
  -moz-transition-property: top, left; }
  .tile.tile-2 {
    background: #eee4da;
    box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); }
  .tile.tile-4 {
    background: #ede0c8;
    box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); }
  .tile.tile-8 {
    color: #f9f6f2;
    background: #f2b179; }
  .tile.tile-16 {
    color: #f9f6f2;
    background: #f59563; }
  .tile.tile-32 {
    color: #f9f6f2;
    background: #f67c5f; }
  .tile.tile-64 {
    color: #f9f6f2;
    background: #f65e3b; }
  .tile.tile-128 {
    color: #f9f6f2;
    background: #edcf72;
    box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286);
    font-size: 45px; }
    @media screen and (max-width: 480px) {
      .tile.tile-128 {
        font-size: 25px; } }
  .tile.tile-256 {
    color: #f9f6f2;
    background: #edcc61;
    box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048);
    font-size: 45px; }
    @media screen and (max-width: 480px) {
      .tile.tile-256 {
        font-size: 25px; } }
  .tile.tile-512 {
    color: #f9f6f2;
    background: #edc850;
    box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381);
    font-size: 45px; }
    @media screen and (max-width: 480px) {
      .tile.tile-512 {
        font-size: 25px; } }
  .tile.tile-1024 {
    color: #f9f6f2;
    background: #edc53f;
    box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571);
    font-size: 35px; }
    @media screen and (max-width: 480px) {
      .tile.tile-1024 {
        font-size: 15px; } }
  .tile.tile-2048 {
    color: #f9f6f2;
    background: #edc22e;
    box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333);
    font-size: 35px; }
    @media screen and (max-width: 480px) {
      .tile.tile-2048 {
        font-size: 15px; } }

@-webkit-keyframes appear {
  0% {
    opacity: 0;
    -webkit-transform: scale(0);
    -moz-transform: scale(0); }

  100% {
    opacity: 1;
    -webkit-transform: scale(1);
    -moz-transform: scale(1); } }

@-moz-keyframes appear {
  0% {
    opacity: 0;
    -webkit-transform: scale(0);
    -moz-transform: scale(0); }

  100% {
    opacity: 1;
    -webkit-transform: scale(1);
    -moz-transform: scale(1); } }

@keyframes appear {
  0% {
    opacity: 0;
    -webkit-transform: scale(0);
    -moz-transform: scale(0); }

  100% {
    opacity: 1;
    -webkit-transform: scale(1);
    -moz-transform: scale(1); } }

.tile-new {
  -webkit-animation: appear 200ms ease 100ms;
  -moz-animation: appear 200ms ease 100ms;
  -webkit-animation-fill-mode: both;
  -moz-animation-fill-mode: both; }

@-webkit-keyframes pop {
  0% {
    -webkit-transform: scale(0);
    -moz-transform: scale(0); }

  50% {
    -webkit-transform: scale(1.2);
    -moz-transform: scale(1.2); }

  100% {
    -webkit-transform: scale(1);
    -moz-transform: scale(1); } }

@-moz-keyframes pop {
  0% {
    -webkit-transform: scale(0);
    -moz-transform: scale(0); }

  50% {
    -webkit-transform: scale(1.2);
    -moz-transform: scale(1.2); }

  100% {
    -webkit-transform: scale(1);
    -moz-transform: scale(1); } }

@keyframes pop {
  0% {
    -webkit-transform: scale(0);
    -moz-transform: scale(0); }

  50% {
    -webkit-transform: scale(1.2);
    -moz-transform: scale(1.2); }

  100% {
    -webkit-transform: scale(1);
    -moz-transform: scale(1); } }

.tile-merged {
  z-index: 20;
  -webkit-animation: pop 200ms ease 100ms;
  -moz-animation: pop 200ms ease 100ms;
  -webkit-animation-fill-mode: both;
  -moz-animation-fill-mode: both; }

.game-intro {
  margin-bottom: 0; }

.game-explanation {
  margin-top: 50px; }

.sharing {
  margin-top: 20px;
  text-align: center; }
  .sharing > iframe, .sharing > span, .sharing > form {
    display: inline-block;
    vertical-align: middle; }

@media screen and (max-width: 480px) {
  html, body {
    font-size: 15px; }

  body {
    margin: 20px 0;
    padding: 0 20px; }

  h1.title {
    font-size: 27px;
    margin-top: 15px; }

  .container {
    width: 280px;
    margin: 0 auto; }

  .score-container, .best-container {
    margin-top: 0;
    padding: 15px 10px;
    min-width: 40px; }

  .heading {
    margin-bottom: 10px; }

  .game-container {
    margin-top: 40px;
    position: relative;
    padding: 10px;
    cursor: default;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    background: #bbada0;
    border-radius: 6px;
    width: 280px;
    height: 280px;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box; }
    .game-container .game-message {
      display: none;
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      background: rgba(238, 228, 218, 0.5);
      z-index: 100;
      text-align: center;
      -webkit-animation: fade-in 800ms ease 1200ms;
      -moz-animation: fade-in 800ms ease 1200ms;
      -webkit-animation-fill-mode: both;
      -moz-animation-fill-mode: both; }
      .game-container .game-message p {
        font-size: 60px;
        font-weight: bold;
        height: 60px;
        line-height: 60px;
        margin-top: 222px; }
      .game-container .game-message .lower {
        display: block;
        margin-top: 59px; }
      .game-container .game-message a {
        display: inline-block;
        background: #8f7a66;
        border-radius: 3px;
        padding: 0 20px;
        text-decoration: none;
        color: #f9f6f2;
        height: 40px;
        line-height: 42px;
        margin-left: 9px; }
      .game-container .game-message .score-sharing {
        display: inline-block;
        vertical-align: middle;
        margin-left: 10px; }
      .game-container .game-message.game-won {
        background: rgba(237, 194, 46, 0.5);
        color: #f9f6f2; }
      .game-container .game-message.game-won, .game-container .game-message.game-over {
        display: block; }

  .grid-container {
    position: absolute;
    z-index: 1; }

  .grid-row {
    margin-bottom: 10px; }
    .grid-row:last-child {
      margin-bottom: 0; }
    .grid-row:after {
      content: "";
      display: block;
      clear: both; }

  .grid-cell {
    width: 57.5px;
    height: 57.5px;
    margin-right: 10px;
    float: left;
    border-radius: 3px;
    background: rgba(238, 228, 218, 0.35); }
    .grid-cell:last-child {
      margin-right: 0; }

  .tile-container {
    position: absolute;
    z-index: 2; }

  .tile {
    width: 57.5px;
    height: 57.5px;
    line-height: 67.5px; }
    .tile.tile-position-1-1 {
      position: absolute;
      left: 0px;
      top: 0px; }
    .tile.tile-position-1-2 {
      position: absolute;
      left: 0px;
      top: 68px; }
    .tile.tile-position-1-3 {
      position: absolute;
      left: 0px;
      top: 135px; }
    .tile.tile-position-1-4 {
      position: absolute;
      left: 0px;
      top: 203px; }
    .tile.tile-position-2-1 {
      position: absolute;
      left: 68px;
      top: 0px; }
    .tile.tile-position-2-2 {
      position: absolute;
      left: 68px;
      top: 68px; }
    .tile.tile-position-2-3 {
      position: absolute;
      left: 68px;
      top: 135px; }
    .tile.tile-position-2-4 {
      position: absolute;
      left: 68px;
      top: 203px; }
    .tile.tile-position-3-1 {
      position: absolute;
      left: 135px;
      top: 0px; }
    .tile.tile-position-3-2 {
      position: absolute;
      left: 135px;
      top: 68px; }
    .tile.tile-position-3-3 {
      position: absolute;
      left: 135px;
      top: 135px; }
    .tile.tile-position-3-4 {
      position: absolute;
      left: 135px;
      top: 203px; }
    .tile.tile-position-4-1 {
      position: absolute;
      left: 203px;
      top: 0px; }
    .tile.tile-position-4-2 {
      position: absolute;
      left: 203px;
      top: 68px; }
    .tile.tile-position-4-3 {
      position: absolute;
      left: 203px;
      top: 135px; }
    .tile.tile-position-4-4 {
      position: absolute;
      left: 203px;
      top: 203px; }

  .game-container {
    margin-top: 20px; }

  .tile {
    font-size: 35px; }

  .game-message p {
    font-size: 30px !important;
    height: 30px !important;
    line-height: 30px !important;
    margin-top: 90px !important; }
  .game-message .lower {
    margin-top: 30px !important; }

  .sharing > iframe, .sharing > span, .sharing > form {
    display: block;
    margin: 0 auto;
    margin-bottom: 20px; } }
.pp-donate button {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  display: inline-block;
  background: #8f7a66;
  border-radius: 3px;
  padding: 0 20px;
  text-decoration: none;
  color: #f9f6f2;
  height: 40px;
  line-height: 42px; }
  .pp-donate button img {
    vertical-align: -4px;
    margin-right: 8px; }

.btc-donate {
  position: relative;
  margin-left: 10px;
  display: inline-block;
  background: #8f7a66;
  border-radius: 3px;
  padding: 0 20px;
  text-decoration: none;
  color: #f9f6f2;
  height: 40px;
  line-height: 42px;
  cursor: pointer; }
  .btc-donate img {
    vertical-align: -4px;
    margin-right: 8px; }
  .btc-donate a {
    color: #f9f6f2;
    text-decoration: none;
    font-weight: normal; }
  .btc-donate .address {
    cursor: auto;
    position: absolute;
    width: 340px;
    right: 50%;
    margin-right: -170px;
    padding-bottom: 7px;
    top: -30px;
    opacity: 0;
    pointer-events: none;
    -webkit-transition: 400ms ease;
    -moz-transition: 400ms ease;
    -webkit-transition-property: top, opacity;
    -moz-transition-property: top, opacity; }
    .btc-donate .address:after {
      position: absolute;
      border-top: 10px solid #bbada0;
      border-right: 7px solid transparent;
      border-left: 7px solid transparent;
      content: "";
      bottom: 0px;
      left: 50%;
      margin-left: -7px; }
    .btc-donate .address code {
      background-color: #bbada0;
      padding: 10px 15px;
      width: 100%;
      border-radius: 3px;
      line-height: 1;
      font-weight: normal;
      font-size: 15px;
      font-family: Consolas, "Liberation Mono", Courier, monospace;
      text-align: center; }
  .btc-donate:hover .address, .btc-donate .address:hover .address {
    opacity: 1;
    top: -45px;
    pointer-events: auto; }
  @media screen and (max-width: 480px) {
    .btc-donate {
      width: 120px; }
      .btc-donate .address {
        margin-right: -150px;
        width: 300px; }
        .btc-donate .address code {
          font-size: 13px; }
        .btc-donate .address:after {
          left: 50%;
          bottom: 2px; } }
	</style>
  <!--link rel="shortcut icon" href="http://gabrielecirulli.github.io/2048/favicon.ico">
  <link rel="apple-touch-icon" href="http://gabrielecirulli.github.io/2048/meta/apple-touch-icon.png"-->
  <meta name="apple-mobile-web-app-capable" content="yes">

  <meta name="HandheldFriendly" content="True">
  <meta name="MobileOptimized" content="320">
  <meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0, maximum-scale=1, user-scalable=no">

  <meta property="og:title" content="2048 game">
  <meta property="og:site_name" content="2048 game">
  <meta property="og:description" content="Join the numbers and get to the 2048 tile! Careful: this game is extremely addictive!">
  <!--meta property="og:image" content="http://gabrielecirulli.github.io/2048/meta/og_image.png"-->
</head>
<body data-twttr-rendered="true" style="">
  <div class="container">
    <div class="heading">
      <h1 class="title">2048</h1>
      <div class="scores-container">
        <div class="score-container">0</div>
        <div class="best-container">12972</div>
      </div>
    </div>
    <p class="game-intro">Join the numbers and get to the <strong>2048 tile!</strong></p>

    <div class="game-container">
      <div class="game-message">
        <p></p>
        <div class="lower">
          <a class="retry-button">Try again</a>
          <div class="score-sharing"></div>
        </div>
      </div>

      <div class="grid-container">
        <div class="grid-row">
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
        </div>
        <div class="grid-row">
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
        </div>
        <div class="grid-row">
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
        </div>
        <div class="grid-row">
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
          <div class="grid-cell"></div>
        </div>
      </div>

      <div class="tile-container"><div class="tile tile-2 tile-position-3-4 tile-new">2</div><div class="tile tile-2 tile-position-4-2 tile-new">2</div></div>
    </div>

    <p class="game-explanation">
      <strong class="important">How to play:</strong> Use your <strong>arrow keys</strong> to move the tiles. When two tiles with the same number touch, they <strong>merge into one!</strong>
    </p>
    <hr>
    <p>
    Created by <a href="http://gabrielecirulli.com/" target="_blank">Gabriele Cirulli.</a> Based on <a href="https://itunes.apple.com/us/app/1024!/id823499224" target="_blank">1024 by Veewo Studio</a> and conceptually similar to <a href="http://asherv.com/threes/" target="_blank">Threes by Asher Vollmer.</a>
    </p>
    <!--div class="sharing">
      <iframe id="twitter-widget-0" scrolling="no" frameborder="0" allowtransparency="true" src="./2048_files/tweet_button.1393899192.htm" class="twitter-share-button twitter-tweet-button twitter-count-horizontal" title="Twitter Tweet Button" data-twttr-rendered="true" style="width: 110px; height: 20px;"></iframe>
      <script async="" src="./2048_files/analytics.js"></script><script id="twitter-wjs" src="./2048_files/widgets.js"></script><script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>

      <form class="pp-donate" action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
        <input type="hidden" name="cmd" value="_s-xclick">
        <input type="hidden" name="hosted_button_id" value="NVNPJLTBZ8AME">
        <button name="submit"><img src="./2048_files/icon_pp.svg">Donate</button>
        <img alt="" border="0" src="./2048_files/pixel.gif" width="1" height="1">
      </form>

      <span class="btc-donate">
        <a href="bitcoin:1Ec6onfsQmoP9kkL3zkpB6c5sA4PVcXU2i">
          <img src="./2048_files/icon_bitcoin.svg">Donate BTC
        </a>
        <span class="address"><code>1Ec6onfsQmoP9kkL3zkpB6c5sA4PVcXU2i</code></span>
      </span>

    </div-->
  </div>

  <!--script src="./2048_files/animframe_polyfill.js"></script-->
  <script>
  (function() {
	  var lastTime = 0;
	  var vendors = ['webkit', 'moz'];
	  for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
		window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
		window.cancelAnimationFrame =
		window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
	  }

	  if (!window.requestAnimationFrame) {
		window.requestAnimationFrame = function(callback, element) {
		  var currTime = new Date().getTime();
		  var timeToCall = Math.max(0, 16 - (currTime - lastTime));
		  var id = window.setTimeout(function() { callback(currTime + timeToCall); },
		  timeToCall);
		  lastTime = currTime + timeToCall;
		  return id;
		};
	  }

	  if (!window.cancelAnimationFrame) {
		window.cancelAnimationFrame = function(id) {
		  clearTimeout(id);
		};
	  }
	}());
  </script>
  
  <!--script src="./2048_files/keyboard_input_manager.js"></script-->
  <script>
  function KeyboardInputManager() {
	  this.events = {};

	  this.listen();
	}

	KeyboardInputManager.prototype.on = function (event, callback) {
	  if (!this.events[event]) {
		this.events[event] = [];
	  }
	  this.events[event].push(callback);
	};

	KeyboardInputManager.prototype.emit = function (event, data) {
	  var callbacks = this.events[event];
	  if (callbacks) {
		callbacks.forEach(function (callback) {
		  callback(data);
		});
	  }
	};

	KeyboardInputManager.prototype.listen = function () {
	  var self = this;

	  var map = {
		38: 0, // Up
		39: 1, // Right
		40: 2, // Down
		37: 3, // Left
		75: 0, // vim keybindings
		76: 1,
		74: 2,
		72: 3,
		87: 0, // W
		68: 1, // D
		83: 2, // S
		65: 3  // A
	  };

	  document.addEventListener("keydown", function (event) {
		var modifiers = event.altKey || event.ctrlKey || event.metaKey ||
						event.shiftKey;
		var mapped    = map[event.which];

		if (!modifiers) {
		  if (mapped !== undefined) {
			event.preventDefault();
			self.emit("move", mapped);
		  }

		  if (event.which === 32) self.restart.bind(self)(event);
		}
	  });

	  var retry = document.getElementsByClassName("retry-button")[0];
	  retry.addEventListener("click", this.restart.bind(this));
	  retry.addEventListener("touchend", this.restart.bind(this));

	  // Listen to swipe events
	  var touchStartClientX, touchStartClientY;
	  var gameContainer = document.getElementsByClassName("game-container")[0];

	  gameContainer.addEventListener("touchstart", function (event) {
		if (event.touches.length > 1) return;

		touchStartClientX = event.touches[0].clientX;
		touchStartClientY = event.touches[0].clientY;
		event.preventDefault();
	  });

	  gameContainer.addEventListener("touchmove", function (event) {
		event.preventDefault();
	  });

	  gameContainer.addEventListener("touchend", function (event) {
		if (event.touches.length > 0) return;

		var dx = event.changedTouches[0].clientX - touchStartClientX;
		var absDx = Math.abs(dx);

		var dy = event.changedTouches[0].clientY - touchStartClientY;
		var absDy = Math.abs(dy);

		if (Math.max(absDx, absDy) > 10) {
		  // (right : left) : (down : up)
		  self.emit("move", absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0));
		}
	  });
	};

	KeyboardInputManager.prototype.restart = function (event) {
	  event.preventDefault();
	  this.emit("restart");
	};
  </script>
  
  <!--script src="./2048_files/html_actuator.js"></script-->
  <script>
	function HTMLActuator() {
	  this.tileContainer    = document.querySelector(".tile-container");
	  this.scoreContainer   = document.querySelector(".score-container");
	  this.bestContainer    = document.querySelector(".best-container");
	  this.messageContainer = document.querySelector(".game-message");
	  this.sharingContainer = document.querySelector(".score-sharing");

	  this.score = 0;
	}

	HTMLActuator.prototype.actuate = function (grid, metadata) {
	  var self = this;

	  window.requestAnimationFrame(function () {
		self.clearContainer(self.tileContainer);

		grid.cells.forEach(function (column) {
		  column.forEach(function (cell) {
			if (cell) {
			  self.addTile(cell);
			}
		  });
		});

		self.updateScore(metadata.score);
		self.updateBestScore(metadata.bestScore);

		if (metadata.over) self.message(false); // You lose
		if (metadata.won) self.message(true); // You win!
	  });
	};

	HTMLActuator.prototype.restart = function () {
	  if (typeof ga !== "undefined") {
		ga("send", "event", "game", "restart");
	  }
	  this.clearMessage();
	};

	HTMLActuator.prototype.clearContainer = function (container) {
	  while (container.firstChild) {
		container.removeChild(container.firstChild);
	  }
	};

	HTMLActuator.prototype.addTile = function (tile) {
	  var self = this;

	  var element   = document.createElement("div");
	  var position  = tile.previousPosition || { x: tile.x, y: tile.y };
	  positionClass = this.positionClass(position);

	  // We can't use classlist because it somehow glitches when replacing classes
	  var classes = ["tile", "tile-" + tile.value, positionClass];
	  this.applyClasses(element, classes);

	  element.textContent = tile.value;

	  if (tile.previousPosition) {
		// Make sure that the tile gets rendered in the previous position first
		window.requestAnimationFrame(function () {
		  classes[2] = self.positionClass({ x: tile.x, y: tile.y });
		  self.applyClasses(element, classes); // Update the position
		});
	  } else if (tile.mergedFrom) {
		classes.push("tile-merged");
		this.applyClasses(element, classes);

		// Render the tiles that merged
		tile.mergedFrom.forEach(function (merged) {
		  self.addTile(merged);
		});
	  } else {
		classes.push("tile-new");
		this.applyClasses(element, classes);
	  }

	  // Put the tile on the board
	  this.tileContainer.appendChild(element);
	};

	HTMLActuator.prototype.applyClasses = function (element, classes) {
	  element.setAttribute("class", classes.join(" "));
	};

	HTMLActuator.prototype.normalizePosition = function (position) {
	  return { x: position.x + 1, y: position.y + 1 };
	};

	HTMLActuator.prototype.positionClass = function (position) {
	  position = this.normalizePosition(position);
	  return "tile-position-" + position.x + "-" + position.y;
	};

	HTMLActuator.prototype.updateScore = function (score) {
	  this.clearContainer(this.scoreContainer);

	  var difference = score - this.score;
	  this.score = score;

	  this.scoreContainer.textContent = this.score;

	  if (difference > 0) {
		var addition = document.createElement("div");
		addition.classList.add("score-addition");
		addition.textContent = "+" + difference;

		this.scoreContainer.appendChild(addition);
	  }
	};

	HTMLActuator.prototype.updateBestScore = function (bestScore) {
	  this.bestContainer.textContent = bestScore;
	};

	HTMLActuator.prototype.message = function (won) {
	  var type    = won ? "game-won" : "game-over";
	  var message = won ? "You win!" : "Game over!";

	  if (typeof ga !== "undefined") {
		ga("send", "event", "game", "end", type, this.score);
	  }

	  this.messageContainer.classList.add(type);
	  this.messageContainer.getElementsByTagName("p")[0].textContent = message;

	  this.clearContainer(this.sharingContainer);
	  this.sharingContainer.appendChild(this.scoreTweetButton());
	  twttr.widgets.load();
	};

	HTMLActuator.prototype.clearMessage = function () {
	  this.messageContainer.classList.remove("game-won", "game-over");
	};

	HTMLActuator.prototype.scoreTweetButton = function () {
	  var tweet = document.createElement("a");
	  tweet.classList.add("twitter-share-button");
	  tweet.setAttribute("href", "https://twitter.com/share");
	  tweet.setAttribute("data-via", "gabrielecirulli");
	  tweet.textContent = "Tweet";

	  var text = "I scored " + this.score + " points at 2048, a game where you " +
				 "join numbers to score high! #2048game";
	  tweet.setAttribute("data-text", text);

	  return tweet;
	};
  </script>
  
  <!--script src="./2048_files/grid.js"></script-->
  <script>
	function Grid(size) {
	  this.size = size;

	  this.cells = [];

	  this.build();
	}

	// Build a grid of the specified size
	Grid.prototype.build = function () {
	  for (var x = 0; x < this.size; x++) {
		var row = this.cells[x] = [];

		for (var y = 0; y < this.size; y++) {
		  row.push(null);
		}
	  }
	};

	// Find the first available random position
	Grid.prototype.randomAvailableCell = function () {
	  var cells = this.availableCells();

	  if (cells.length) {
		return cells[Math.floor(Math.random() * cells.length)];
	  }
	};

	Grid.prototype.availableCells = function () {
	  var cells = [];

	  this.eachCell(function (x, y, tile) {
		if (!tile) {
		  cells.push({ x: x, y: y });
		}
	  });

	  return cells;
	};

	// Call callback for every cell
	Grid.prototype.eachCell = function (callback) {
	  for (var x = 0; x < this.size; x++) {
		for (var y = 0; y < this.size; y++) {
		  callback(x, y, this.cells[x][y]);
		}
	  }
	};

	// Check if there are any cells available
	Grid.prototype.cellsAvailable = function () {
	  return !!this.availableCells().length;
	};

	// Check if the specified cell is taken
	Grid.prototype.cellAvailable = function (cell) {
	  return !this.cellOccupied(cell);
	};

	Grid.prototype.cellOccupied = function (cell) {
	  return !!this.cellContent(cell);
	};

	Grid.prototype.cellContent = function (cell) {
	  if (this.withinBounds(cell)) {
		return this.cells[cell.x][cell.y];
	  } else {
		return null;
	  }
	};

	// Inserts a tile at its position
	Grid.prototype.insertTile = function (tile) {
	  this.cells[tile.x][tile.y] = tile;
	};

	Grid.prototype.removeTile = function (tile) {
	  this.cells[tile.x][tile.y] = null;
	};

	Grid.prototype.withinBounds = function (position) {
	  return position.x >= 0 && position.x < this.size &&
			 position.y >= 0 && position.y < this.size;
	};
  </script>
  
  <!--script src="./2048_files/tile.js"></script-->
  <script>
	 function Tile(position, value) {
	  this.x                = position.x;
	  this.y                = position.y;
	  this.value            = value || 2;

	  this.previousPosition = null;
	  this.mergedFrom       = null; // Tracks tiles that merged together
	}

	Tile.prototype.savePosition = function () {
	  this.previousPosition = { x: this.x, y: this.y };
	};

	Tile.prototype.updatePosition = function (position) {
	  this.x = position.x;
	  this.y = position.y;
	};
  </script>
  
  <!--script src="./2048_files/local_score_manager.js"></script-->
  <script>
	 window.fakeStorage = {
	  _data: {},

	  setItem: function (id, val) {
		return this._data[id] = String(val);
	  },

	  getItem: function (id) {
		return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
	  },

	  removeItem: function (id) {
		return delete this._data[id];
	  },

	  clear: function () {
		return this._data = {};
	  }
	};

	function LocalScoreManager() {
	  var localSupported = !!window.localStorage;

	  this.key     = "bestScore";
	  this.storage = localSupported ? window.localStorage : window.fakeStorage;
	}

	LocalScoreManager.prototype.get = function () {
	  return this.storage.getItem(this.key) || 0;
	};

	LocalScoreManager.prototype.set = function (score) {
	  this.storage.setItem(this.key, score);
	};
  </script>
  
  <!--script src="./2048_files/game_manager.js"></script-->
  <script>
	function GameManager(size, InputManager, Actuator, ScoreManager) {
	  this.size         = size; // Size of the grid
	  this.inputManager = new InputManager;
	  this.scoreManager = new ScoreManager;
	  this.actuator     = new Actuator;

	  this.startTiles   = 2;

	  this.inputManager.on("move", this.move.bind(this));
	  this.inputManager.on("restart", this.restart.bind(this));

	  this.setup();
	}

	// Restart the game
	GameManager.prototype.restart = function () {
	  this.actuator.restart();
	  this.setup();
	};

	// Set up the game
	GameManager.prototype.setup = function () {
	  this.grid         = new Grid(this.size);

	  this.score        = 0;
	  this.over         = false;
	  this.won          = false;

	  // Add the initial tiles
	  this.addStartTiles();

	  // Update the actuator
	  this.actuate();
	};

	// Set up the initial tiles to start the game with
	GameManager.prototype.addStartTiles = function () {
	  for (var i = 0; i < this.startTiles; i++) {
		this.addRandomTile();
	  }
	};

	// Adds a tile in a random position
	GameManager.prototype.addRandomTile = function () {
	  if (this.grid.cellsAvailable()) {
		var value = Math.random() < 0.9 ? 2 : 4;
		var tile = new Tile(this.grid.randomAvailableCell(), value);

		this.grid.insertTile(tile);
	  }
	};

	// Sends the updated grid to the actuator
	GameManager.prototype.actuate = function () {
	  if (this.scoreManager.get() < this.score) {
		this.scoreManager.set(this.score);
	  }

	  this.actuator.actuate(this.grid, {
		score:     this.score,
		over:      this.over,
		won:       this.won,
		bestScore: this.scoreManager.get()
	  });

	};

	// Save all tile positions and remove merger info
	GameManager.prototype.prepareTiles = function () {
	  this.grid.eachCell(function (x, y, tile) {
		if (tile) {
		  tile.mergedFrom = null;
		  tile.savePosition();
		}
	  });
	};

	// Move a tile and its representation
	GameManager.prototype.moveTile = function (tile, cell) {
	  this.grid.cells[tile.x][tile.y] = null;
	  this.grid.cells[cell.x][cell.y] = tile;
	  tile.updatePosition(cell);
	};

	// Move tiles on the grid in the specified direction
	GameManager.prototype.move = function (direction) {
	  // 0: up, 1: right, 2:down, 3: left
	  var self = this;

	  if (this.over || this.won) return; // Don't do anything if the game's over

	  var cell, tile;

	  var vector     = this.getVector(direction);
	  var traversals = this.buildTraversals(vector);
	  var moved      = false;

	  // Save the current tile positions and remove merger information
	  this.prepareTiles();

	  // Traverse the grid in the right direction and move tiles
	  traversals.x.forEach(function (x) {
		traversals.y.forEach(function (y) {
		  cell = { x: x, y: y };
		  tile = self.grid.cellContent(cell);

		  if (tile) {
			var positions = self.findFarthestPosition(cell, vector);
			var next      = self.grid.cellContent(positions.next);

			// Only one merger per row traversal?
			if (next && next.value === tile.value && !next.mergedFrom) {
			  var merged = new Tile(positions.next, tile.value * 2);
			  merged.mergedFrom = [tile, next];

			  self.grid.insertTile(merged);
			  self.grid.removeTile(tile);

			  // Converge the two tiles' positions
			  tile.updatePosition(positions.next);

			  // Update the score
			  self.score += merged.value;

			  // The mighty 2048 tile
			  if (merged.value === 2048) self.won = true;
			} else {
			  self.moveTile(tile, positions.farthest);
			}

			if (!self.positionsEqual(cell, tile)) {
			  moved = true; // The tile moved from its original cell!
			}
		  }
		});
	  });

	  if (moved) {
		this.addRandomTile();

		if (!this.movesAvailable()) {
		  this.over = true; // Game over!
		}

		this.actuate();
	  }
	};

	// Get the vector representing the chosen direction
	GameManager.prototype.getVector = function (direction) {
	  // Vectors representing tile movement
	  var map = {
		0: { x: 0,  y: -1 }, // up
		1: { x: 1,  y: 0 },  // right
		2: { x: 0,  y: 1 },  // down
		3: { x: -1, y: 0 }   // left
	  };

	  return map[direction];
	};

	// Build a list of positions to traverse in the right order
	GameManager.prototype.buildTraversals = function (vector) {
	  var traversals = { x: [], y: [] };

	  for (var pos = 0; pos < this.size; pos++) {
		traversals.x.push(pos);
		traversals.y.push(pos);
	  }

	  // Always traverse from the farthest cell in the chosen direction
	  if (vector.x === 1) traversals.x = traversals.x.reverse();
	  if (vector.y === 1) traversals.y = traversals.y.reverse();

	  return traversals;
	};

	GameManager.prototype.findFarthestPosition = function (cell, vector) {
	  var previous;

	  // Progress towards the vector direction until an obstacle is found
	  do {
		previous = cell;
		cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
	  } while (this.grid.withinBounds(cell) &&
			   this.grid.cellAvailable(cell));

	  return {
		farthest: previous,
		next: cell // Used to check if a merge is required
	  };
	};

	GameManager.prototype.movesAvailable = function () {
	  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
	};

	// Check for available matches between tiles (more expensive check)
	GameManager.prototype.tileMatchesAvailable = function () {
	  var self = this;

	  var tile;

	  for (var x = 0; x < this.size; x++) {
		for (var y = 0; y < this.size; y++) {
		  tile = this.grid.cellContent({ x: x, y: y });

		  if (tile) {
			for (var direction = 0; direction < 4; direction++) {
			  var vector = self.getVector(direction);
			  var cell   = { x: x + vector.x, y: y + vector.y };

			  var other  = self.grid.cellContent(cell);

			  if (other && other.value === tile.value) {
				return true; // These two tiles can be merged
			  }
			}
		  }
		}
	  }

	  return false;
	};

	GameManager.prototype.positionsEqual = function (first, second) {
	  return first.x === second.x && first.y === second.y;
	};
  </script>
  
  <!--script src="./2048_files/application.js"></script-->
  <script>
	// Wait till the browser is ready to render the game (avoids glitches)
	window.requestAnimationFrame(function () {
	  new GameManager(4, KeyboardInputManager, HTMLActuator, LocalScoreManager);
	});
  </script>

  <!--script>
    (function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,"script","//www.google-analytics.com/analytics.js","ga");

    ga("create", "UA-42620757-2", "gabrielecirulli.github.io");
    ga("send", "pageview");
  </script-->


</body></html>